/**
 * Created by Richard Radlherr.
 * Date: 10.06.2006 11:39
 */

using System;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using TagTool.GraphicTypes;
using CsGL.Util;
using TagTool.Devices;
using TagTool.Devices.DataStructures;

namespace TagTool.Devices.Mouse
{

	public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

	public sealed class WinMouse : IMouseDevice, IDisposable
	{
		public event MouseDeviceEventHandler MouseDeviceEvent;

		private static WinMouse instance;
		private static int hMouseHook = 0; //Declare mouse hook handle as int.

        private string name;
		private PointD location;
		
		private bool middle;
		private bool left;
		private bool right;

		private MouseDeviceEventArgs mouseArgs;
		private DeviceResolution resolution;
			
		private WinMouse()
		{
			location = new PointD();
			mouseArgs = new MouseDeviceEventArgs();
			
			ScreenSetting s = ScreenSetting.CurrentDisplay;
			resolution = new DeviceResolution(new Axis(0, s.Width, AxisUnit.None, 0), new Axis(0, s.Height, AxisUnit.None, 0), new Axis(0, 0, AxisUnit.None, 0));
			Connect();
		}
		
		~WinMouse()
		{
			Disconnect();
		}
		
		public static WinMouse GetInstance()
		{
			if (instance == null)
			{
				instance = new WinMouse();
			}
			return instance;
        }

        #region Properties

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        public int DeviceId
        {
            get { return 0; }
        }

        public PointD Location
		{
			get { return location; }
		}
		
		public double X
		{
			get { return location.X; }
		}
		
		public double Y
		{
			get { return location.Y; }
		}
		
		public double Z
		{
			get { return location.Z; }
		}

		public bool Middle
		{
			get { return middle; }
		}
		
		public bool Left
		{
			get { return left; }
		}
		
		public bool Right
		{
			get { return right; }
		}
		
		public uint Buttons
		{
			get
			{
				return (uint)((left ? 1 : 0) | (middle ? 2 : 0) | (right ? 4 : 0));
			}
		}
		
		public DeviceResolution Resolution
		{
			get { return resolution; }
        }

        #endregion Properties

        //values from Winuser.h in Microsoft SDK.
		public const int WH_MOUSE_LL 	= 14;	//mouse hook constant

		HookProc MouseHookProcedure; //Declare MouseHookProcedure as HookProc type.

		//Declare wrapper managed POINT class.
		[StructLayout(LayoutKind.Sequential)]
		public class POINT 
		{
			public int x;
			public int y;
		}

		//Declare wrapper managed MouseHookStruct class.
		[StructLayout(LayoutKind.Sequential)]
		public class MouseHookStruct 
		{
			public POINT pt;
			public int hwnd;
			public int wHitTestCode;
			public int dwExtraInfo;
		}

		[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
		public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

		[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
		public static extern bool UnhookWindowsHookEx(int idHook);
		
		[DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
		public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);  

		public void Connect()
		{
			// install Mouse hook 
			if(hMouseHook == 0)
			{
				// Create an instance of HookProc.
				MouseHookProcedure = new HookProc(MouseHookProc);

				hMouseHook = SetWindowsHookEx( WH_MOUSE_LL,
					MouseHookProcedure, 
					Marshal.GetHINSTANCE(
						Assembly.GetExecutingAssembly().GetModules()[0]),
					0);

				//If SetWindowsHookEx fails.
				if(hMouseHook == 0 )	{
					Disconnect();
					throw new Exception("SetWindowsHookEx failed.");
				}
			}
		}

		public void Disconnect()
		{
			bool retMouse =true;
			if(hMouseHook != 0)
			{
				retMouse = UnhookWindowsHookEx(hMouseHook);
				hMouseHook = 0;
			} 

			// TODO: discussion about proper exception handling
			//if (!retMouse) throw new Exception("UnhookWindowsHookEx failed.");
		}

      	private const int WM_MOUSEMOVE = 0x200;
      	private const int WM_LBUTTONDOWN = 0x201;
      	private const int WM_RBUTTONDOWN = 0x204;
      	private const int WM_MBUTTONDOWN = 0x207;
      	private const int WM_LBUTTONUP = 0x202;
      	private const int WM_RBUTTONUP = 0x205;
      	private const int WM_MBUTTONUP = 0x208;
      	private const int WM_LBUTTONDBLCLK = 0x203;
      	private const int WM_RBUTTONDBLCLK = 0x206;
		private const int WM_MBUTTONDBLCLK = 0x209;

		private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
		{
			if (nCode >= 0) 
			{
				MouseButtons button = MouseButtons.None;

				switch (wParam)
				{
					case WM_LBUTTONUP:
						left = false;
						button = MouseButtons.Left; 
						break;
					case WM_LBUTTONDOWN: 
						left = true;
						button = MouseButtons.Left; 
						break;
					case WM_RBUTTONUP:
						right = false;
						button = MouseButtons.Right; 
						break;
					case WM_RBUTTONDOWN: 
						right = true;
						button = MouseButtons.Right; 
						break;
					case WM_MBUTTONUP: 
						middle = false;
						button = MouseButtons.Right; 
						break;
					case WM_MBUTTONDOWN: 
						middle = true;
						button = MouseButtons.Right; 
						break;
				}

				uint clickCount = 0;
				
				if (button != MouseButtons.None)
				{
					if (wParam==WM_LBUTTONDBLCLK || wParam==WM_RBUTTONDBLCLK) 
					{
						clickCount=2;
					}
					else 
					{
						clickCount=1;
					}
				}
				
				//Marshall the data from callback.
				MouseHookStruct MyMouseHookStruct = (MouseHookStruct) Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
				mouseArgs.Location.X = (((double)MyMouseHookStruct.pt.x - resolution.X.Maximum / 2)/ resolution.X.Maximum) * 200;
				mouseArgs.Location.Y = (((double)MyMouseHookStruct.pt.y - resolution.Y.Maximum / 2)/ resolution.Y.Maximum) * 200;
				mouseArgs.Buttons = (uint)button;
				mouseArgs.Clicks = clickCount;
				OnMove(mouseArgs);
			}
			
			return CallNextHookEx(hMouseHook, nCode, wParam, lParam); 
		}

		public void OnMove(MouseDeviceEventArgs e)
		{
			if (MouseDeviceEvent != null)
			{
				MouseDeviceEvent(this, e);
			}
		}


		public void Dispose()
		{
			Disconnect();
		}
		

        #region IDevice Member


        public bool Available
        {
            get
            {
                return (hMouseHook == 0);
            }
        }

        #endregion
    }
}
